package com.avapir.snake.Actors.PC;

import java.awt.Color;
import java.awt.Graphics;
import java.net.InetAddress;
import java.util.Random;
import java.util.Vector;

import com.avapir.snake.Actors.NPC.Food;
import com.avapir.snake.Core.Painter;
import com.avapir.snake.Networking.HostHandler;
import com.avapir.snake.UI.Game;

/**
 * 
 * @author Alpen Ditrix
 * 
 */
public class Snake {

	/**
	 * 
	 * @author Alpen Ditrix
	 * 
	 */
	private final class Piece {
		/**
		 * X position
		 */
		int x;

		/**
		 * Y position
		 */
		int y;

		/**
		 * Default constructor
		 * 
		 * @param x
		 * @param y
		 */
		public Piece(int x, int y) {
			this.x = x;
			this.y = y;
		}

		@Override
		public Object clone() {
			return new Piece(x, y);
		}

		@Override
		public boolean equals(Object p) {
			Piece myHyperPiece;
			try {
				myHyperPiece = (Piece) p;
			} catch (ClassCastException e) {
				return false;
			}
			return myHyperPiece.x == this.x && myHyperPiece.y == this.y;
		}

		@Override
		public String toString() {
			return "(" + Integer.toString(this.x) + ":"
					+ Integer.toString(this.y) + ")";

		}

		/**
		 * Moves piece to neighbor cell
		 */
		protected void goDown() {
			y += CELL_SIZE;
		}

		/**
		 * Moves piece to neighbor cell
		 */
		protected void goLeft() {
			x -= CELL_SIZE;
		}

		/**
		 * Moves piece to neighbor cell
		 */
		protected void goRight() {
			x += CELL_SIZE;
		}

		/**
		 * Moves piece to neighbor cell
		 */
		protected void goUp() {
			y -= CELL_SIZE;
		}

		/**
		 * Moves piece to neighbor cell in given {@code direction}
		 * 
		 * @param direction
		 *            guess?
		 */
		private void move(Direction direction) {
			try {
				switch (direction) {
				case RIGHT:
					goRight();
					break;
				case UP:
					goUp();
					break;
				case LEFT:
					goLeft();
					break;
				case DOWN:
					goDown();
					break;
				case STOP:
					break;
				default:
					throw new RuntimeException();
				}
			} finally {
				if (x < 0)
					x = Painter.getWidth() - CELL_SIZE;
				else if (x > Painter.getWidth() - CELL_SIZE)
					x = 0;
				if (y < 30)
					y = Painter.getHeight() - CELL_SIZE;
				else if (y > Painter.getHeight() - CELL_SIZE)
					y = 30;
			}
		}
	}

	/**
	 * Size of square cells on game field
	 */
	private static final int CELL_SIZE = Painter.getCellSize();

	/**
	 * Corpses will be painted in this color
	 */
	public static final Color CORPSE_COLOR = new Color(255, 0, 0, 128);

	/**
	 * Used to snakes population control
	 */
	public static int aliveSnakesLeft;

	/**
	 * Affects on snake's appearance
	 */
	private static final byte tailOpacity = 100;

	/**
	 * Is this snake AI-controlled?
	 */
	private boolean usingAI;

	// private boolean vulnerable;

	/**
	 * Direction of this snake's head
	 */
	public Direction direction;

	/**
	 * Each snake has her her own color
	 */
	private Color headColor;

	/**
	 * @deprecated Never locally used, because tailColor draws as shadowed
	 *             headColor. Maybe in future I'll //TODO set it as custom
	 */
	@Deprecated
	@SuppressWarnings("unused")
	private Color tailColor = Color.white;

	/**
	 * MegaGurren-dan was here
	 * 
	 * Counts how many pieces should grow more
	 */
	private int myDrillWasCreatedToPierceTheSkies;

	/**
	 * Main storage of snake. Here is info about all pieces
	 */
	private Vector<Piece> pieces;

	/**
	 * Name of this snake
	 */
	public String playerName;

	/**
	 * A distinctive feature used for comparing snakes by {@code equlas(Object)}
	 * method
	 */
	private int ID;

	/**
	 * Score collected by this snake
	 */
	private double score;

	/**
	 * Is this snake already alive?
	 */
	private boolean isAlive;

	/* Scores */
	/* DO NOT FORGET TO FIX AI-TARGETING WIEGHT AFTER SCORES EDITING!!! */

	/**
	 * Score modifier
	 */
	private static final double BIG_MULTIPLIER = 3;

	/**
	 * Score modifier
	 */
	private static final double VALHALLA_MULTIPLIER = 50;

	/**
	 * Score modifier
	 */
	private static final double DEAD_TAIL_MULTIPLER = 1.5;

	/**
	 * Score modifier
	 */
	private static final double ALIVE_TAIL_MULTIPLIER = 1.5;

	/**
	 * Score modifier
	 */
	private static final double HEAD_MULTIPLIER = 2;

	/**
	 * Score modifier
	 */
	private static final double STD_SCORE = 1;

	/**
	 * Amount of pieces, which will grow for each score collected
	 */
	private static final int STD_GROWER = 1;

	/* Logging */

	/**
	 * I need to know direction of previous step to check if it had changed
	 */
	private Direction prevDir;

	/**
	 * Creates snake with {@code length == 1}
	 * 
	 * @deprecated Never locally used
	 * @param x
	 *            X head position
	 * @param y
	 *            Y head position
	 * @param ai
	 *            AI-controlled?
	 */
	@Deprecated
	public Snake(int x, int y, boolean ai) {
		init(x, y, 1, ai);
	}

	/**
	 * Creates snake with specified length
	 * 
	 * @deprecated Never locally used
	 * @param x
	 *            X head position
	 * @param y
	 *            Y head position
	 * @param size
	 *            length of created snake
	 * @param ai
	 *            AI-controlled?
	 */
	@Deprecated
	public Snake(int x, int y, int size, boolean ai) {
		init(x, y, size, ai);
	}

	/**
	 * Creates named snake with {@code length == 1}
	 * 
	 * @deprecated Never locally used
	 * @param x
	 *            X head position
	 * @param y
	 *            Y head position
	 * @param ai
	 *            AI-controlled?
	 */
	@Deprecated
	public Snake(String name, int x, int y, boolean ai) {
		playerName = name;
		init(x, y, 1, ai);
	}

	/**
	 * Creates named snake with specified length
	 * 
	 * @param name
	 *            specified player name
	 * @param x
	 *            X head position
	 * @param y
	 *            Y head position
	 * @param size
	 *            length of created snake
	 * @param ai
	 *            AI-controlled?
	 */
	public Snake(String name, int x, int y, int size, boolean ai) {
		playerName = name;
		init(x, y, size, ai);
	}

	/**
	 * NETWORKING for adding alive player TODO: Check if it right written
	 * 
	 * @param name
	 * @param x
	 * @param y
	 * @param size
	 * @param playerAdress
	 */

	public Snake(String name, int x, int y, int size, InetAddress playerAdress) {
		playerName = name;
		init(x, y, size, false);
		HostHandler.addPlayer(playerAdress, this);
	}

	@Deprecated
	public void addAdminDonut() {
		score += 500;
	}

	// @Deprecated
	// public void addAdminGodPower() {
	// vulnerable = !vulnerable;
	// }

	/**
	 * Draws all pieces in specified by {@code isAlive} and {@code headColor}
	 * color
	 * 
	 * @param g
	 */
	public void draw(Graphics g) {
		/* Displays tail */
		if (isAlive) {
			g.setColor(new Color(headColor.getRed(), headColor.getGreen(),
					headColor.getBlue(), tailOpacity));
			for (int i = pieces.size() - 1; i > 0; i--) {
				int x = pieces.get(i).x + CELL_SIZE / 5;
				int y = pieces.get(i).y + CELL_SIZE / 5;
				int x_ = CELL_SIZE * 3 / 5;
				int y_ = CELL_SIZE * 3 / 5;
				g.fillRect(x, y, x_, y_);
			}
		} else {
			g.setColor(new Color(CORPSE_COLOR.getRed(),
					CORPSE_COLOR.getGreen(), CORPSE_COLOR.getBlue(),
					tailOpacity));
			for (int i = pieces.size() - 1; i > 0; i--) {

				if (pieces.get(i) != null)
					g.fillRect(pieces.get(i).x + CELL_SIZE / 5, pieces.get(i).y
							+ CELL_SIZE / 5, CELL_SIZE * 3 / 5,
							CELL_SIZE * 3 / 5);

			}
		}

		/* Displays head */
		g.setColor(headColor);
		// if (!vulnerable) {
		// if (new Random().nextInt(100) > 50)
		// g.setColor(Color.green);
		// }
		if (pieces.get(0) != null)
			g.fillRect(pieces.get(0).x, pieces.get(0).y, CELL_SIZE, CELL_SIZE);
	}

	/**
	 * @deprecated
	 * @param g
	 *            Graphics to draw on which
	 */
	@Deprecated
	public void drawLoser(Graphics g) {
		tailColor = Color.red;
		draw(g);
	}

	/**
	 * @deprecated
	 * @param g
	 *            Graphics to draw on which
	 */
	@Deprecated
	public void drawWinner(Graphics g) {
		tailColor = Color.green;
		draw(g);
	}

	@Override
	public boolean equals(Object obj) {
		// TODO I don't know how to write equals
		if (obj instanceof Snake) {
			return this.ID == ((Snake) obj).ID;
		} else
			return false;
	}

	/**
	 * @deprecated
	 * @return color of this snake
	 */
	@Deprecated
	public Color getColor() {
		return headColor;
	}

	/**
	 * @return current snake`s movement direction
	 */
	public Direction getDirection() {
		return direction;
	}

	/**
	 * @return current head-piece X position
	 */
	public int getHeadX() {
		return pieces.get(0).x;
	}

	/**
	 * @return current head-piece Y position
	 */
	public int getHeadY() {
		return pieces.get(0).y;
	}

	/**
	 * @deprecated
	 * @return ID of this snake
	 */
	@Deprecated
	public int getID() {
		return ID;
	}

	/**
	 * @return amount of pieces
	 */
	public int getLength() {
		return pieces.size();
	}

	/**
	 * @return name of this snake
	 */
	public String getPlayerName() {
		return playerName;
	}

	/**
	 * @return Score of this snake
	 */
	public double getScore() {
		return score;
	}

	/**
	 * @return Is this snake is alive yet?
	 */
	public boolean isAlive() {
		return isAlive;
	}

	/**
	 * Checks is this snake contains cell with coords <I>x</I> and <I>y</I>
	 * 
	 * @param x
	 *            X pending cell position
	 * @param y
	 *            Y pending cell position
	 * @return index of piece in vector, which is covered by pending cell or -1
	 *         if there's no covered pieces
	 */
	public int isCovering(int x, int y) {
		for (int i = 0; i < pieces.size(); i++) {
			if (pieces.get(i) != null)
				if (pieces.get(i).x == x && pieces.get(i).y == y)
					return i;
		}
		return -1;
	}

	/**
	 * @deprecated
	 * @return
	 */
	@Deprecated
	public boolean isUsingAI() {
		return usingAI;
	}

	/**
	 * This method performs compares head position with all other objects on the
	 * field and snake will "eat" it if someone will be found
	 */
	public void lunch() {
		if (isAlive && direction != Direction.STOP) {
			/* Is snake eats itself? */
			Piece head = pieces.get(0);
			for (int i = 1; i < pieces.size(); i++) {
				if (head.x == pieces.get(i).x && head.y == pieces.get(i).y) {
					this.kill();
				}
			}

			/* Is snake eats some food? or takes some buff? */
			Vector<Food> food = Game.getFood();
			for (int i = 0; i < food.size(); i++) {
				if (head.x == food.get(i).getX()
						&& head.y == food.get(i).getY()) {
					takeBuff(i);
					eat(i);
					food.set(i, new Food());
					break;
				}
			}
			/* Is snake eats some another snake? */
			for (Snake s : Game.getSnakes()) {
				// && s.vulnerable вызывает pieces=null, если this.length <
				// s.lenght, но this.vulnerable = false, a s.vulnerable = true
				// && s.vulnerable здесь позволяет не подыхать
				if (!s.equals(this)) {
					int res = s.isCovering(head.x, head.y);
					if (res != -1) {
						if (s.isAlive) {
							// && s.vulnerable здесь позволяет жрать змей жопой
							if (res != 0) {
								int cutted = s.pieces.size() - res;
								s.trim(cutted);
								cutted /= 2;
								score += cutted * ALIVE_TAIL_MULTIPLIER;
								myDrillWasCreatedToPierceTheSkies += cutted;
							} else {
								if (pieces.size() > s.pieces.size()) {
									s.kill();
									score += s.getLength() * HEAD_MULTIPLIER;
									s.pieces.set(0, null);
								} else {
									this.kill();
									pieces.set(0, null);
								}
							}
						} else {
							s.pieces.set(res, null);
							score += DEAD_TAIL_MULTIPLER;
							myDrillWasCreatedToPierceTheSkies++;
						}
					}
				}
			}
		}
		/* Alped Ditrix built and +1'd this ladder */
	}

	/**
	 * This method moves all snake forward in specified direction and grows new
	 * pieces on tail if needed
	 */
	public void move() {
		if (usingAI) {
			direction = AI.paveWayFrom(this, direction);
		}
		if (direction != Direction.STOP) {
			Piece tail = (Piece) pieces.get(pieces.size() - 1).clone();
			for (int i = pieces.size() - 1; i > 0; i--) {

				pieces.set(i, (Piece) pieces.get(i - 1).clone());
			}
			pieces.get(0).move(direction);

			if (myDrillWasCreatedToPierceTheSkies > 0) {
				pieces.add(tail);
				pieces.trimToSize();
				myDrillWasCreatedToPierceTheSkies--;
			}
			if (prevDir != direction) {
			}
			prevDir = direction;
		}
	}

	/**
	 * Used to stop snake or to manual control by user
	 * 
	 * @param direction
	 *            new direction to go
	 * @return old direction
	 */
	public Direction setDirection(Direction direction) {
		Direction old = this.direction;
		this.direction = direction;
		return old;
	}

	/**
	 * @deprecated
	 * @param newID
	 */
	@Deprecated
	public void setID(int newID) {
		ID = newID;
	}

	/**
	 * Used on snake creation
	 * 
	 * @param playerName
	 *            new name
	 */
	public void setPlayerName(String playerName) {
		this.playerName = playerName;
	}

	@Override
	public String toString() {
		return playerName;
	}

	/**
	 * Here performed eating {@linkplain Food} defined as... food!
	 * 
	 * @param i
	 *            food index in storage
	 */
	private void eat(int i) {
		switch (Game.getFood().get(i).getType()) {
		case TYPICAL:
			grow(STD_GROWER);
			score += STD_SCORE;
			break;
		case BIG:
			grow(((Double) (STD_GROWER * BIG_MULTIPLIER)).intValue());
			score += STD_SCORE * BIG_MULTIPLIER;
			break;
		case WALL:
			this.kill();
			break;
		case VALHALLA:
			grow(Painter.getWidth() * Painter.getHeight() - pieces.size());
			score += STD_SCORE * VALHALLA_MULTIPLIER;
			break;
		}
	}

	/**
	 * Schedule pieces "to grow"
	 * 
	 * @param i
	 *            how much pieces must be grown more
	 */
	private void grow(int i) {
		myDrillWasCreatedToPierceTheSkies += i;
	}

	/**
	 * Default snake initialization used by all constructors
	 * 
	 * @param x
	 * @param y
	 * @param size
	 * @param ai
	 */
	private void init(int x, int y, int size, boolean ai) {

		aliveSnakesLeft++;
		ID = (new Random()).nextInt();
		direction = Direction.STOP;
		isAlive = true;
		pieces = new Vector<Piece>();
		for (int i = 0; i < size; i++) {
			pieces.add(new Piece(x, y));
		}
		// pieces.trimToSize();
		score = 0;
		headColor = Color.decode(Integer.toString(this.hashCode()));
		myDrillWasCreatedToPierceTheSkies = 0;
		usingAI = ai;
		// vulnerable = true;
	}

	/**
	 * Used when snake is killed anyway
	 */
	private void kill() {
		// if (vulnerable) {
		isAlive = false;
		setDirection(Direction.STOP);
		aliveSnakesLeft--;
		// }
	}

	/**
	 * Eating {@linkplain Food} which looks as buffs
	 * 
	 * @param i
	 */
	private void takeBuff(int i) {
		switch (Game.getFood().get(i).getType()) {
		case VALHALLA:
			Game.forceWin(this);
			break;
		case INVULNERABILITY:
			// battleLog.println("God power granted!");
			// final Snake vul = this;
			// new Thread(new Runnable() {
			//
			// @Override
			// public void run() {
			// vul.vulnerable = false;
			// try {
			// Thread.sleep(15000);
			// } catch (InterruptedException e) {
			// e.printStackTrace();
			// }
			// vul.vulnerable = true;
			// }
			// }, "Invulnerability timer").start();
			break;
		}
	}

	/**
	 * @deprecated Never locally used
	 */
	@Deprecated
	@SuppressWarnings("unused")
	private void trim() {
		trim(1);
	}

	/**
	 * Used to cut excess pieces, appeared by going another snake through this,
	 * but not through head
	 * 
	 * @param i
	 */
	private void trim(int i) {
		pieces.setSize(pieces.size() - i);
		;
		pieces.trimToSize();
	}
}